/*
 * Copyright (c)  2008 Sebastien Tremblay
 *
 * id3-tag-builder - threads.TagBuilder.java
 *
 * All source and documentation is copyrighted by Sebastien Tremblay
 * (seb at ryders dot net) and made available under the Apache License 2.0.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: TagBuilder.java 33 2010-04-02 07:47:55Z ryders $
 */
package com.empireone.id3tagbuilder.threads;

import com.empireone.id3tagbuilder.classes.Format;
import com.empireone.id3tagbuilder.classes.db.MusicDbException;
import com.empireone.id3tagbuilder.classes.id3.*;
import com.empireone.id3tagbuilder.classes.Path;
import com.empireone.id3tagbuilder.classes.db.MusicAlbum;
import com.empireone.id3tagbuilder.classes.db.MusicDb;
import com.empireone.id3tagbuilder.classes.db.MusicTrack;
import com.empireone.id3tagbuilder.classes.id3.Id3Proxy.ID3Version;
import com.empireone.id3tagbuilder.helpers.FileHelper;
import com.empireone.id3tagbuilder.helpers.StringHelper;
import com.empireone.id3tagbuilder.settings.ApplicationSettings;
import com.empireone.id3tagbuilder.threads.StatusUpdateListener.MsgType;
import java.io.File;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * The <code>TagBuilder</code> class is a runnable interface generating id3 tags
 * for a given album and based on <see>ApplicationSettings</see> defined and
 * <see>Id3Proxy</see> requested.
 *
 * @author Sebastien Tremblay
 */
public class TagBuilder implements Runnable {

    private Format format;
    private Vector<File> files;
    private Path album;
    private ApplicationSettings settings;
    private StatusUpdateListener caller;
    private ResourceBundle messages;
    private MusicDb musicDb;

    /**
     *
     * @param path
     * @param files
     * @param format
     * @param settings
     * @param caller
     * @param musicDb
     */
    public TagBuilder(Path path, Vector<File> files, Format format, ApplicationSettings settings, StatusUpdateListener caller, MusicDb musicDb) {
        this.format = format;
        this.settings = settings;
        this.files = files;
        this.caller = caller;
        this.album = path;
        this.musicDb = musicDb;
    }

    /**
     *
     */
    @Override
    public void run() {

        // logging a few things...
        long startTime = System.currentTimeMillis();
        caller.status(MsgType.JOB_STARTED, StringHelper.substitute(getString("job-started"), Thread.currentThread().getId() + "|" + album.getName() + "|" + files.size()));

        Thread.currentThread().setPriority(Thread.currentThread().getPriority() - 1);

        Id3Proxy mp3p = getTagProxy();
        MusicAlbum musicAlbum = new MusicAlbum();

        if (settings.getEmbedArt()) {
            musicAlbum.merge(findAlbumArt());
        }
        // first off, for each mp3s, let's extract all components
        for (File mp3 : files) {

            MusicAlbum temp = extract(mp3, mp3p.getSupportedExt());
            if (temp == null) {
                caller.status(MsgType.ERROR, "Pattern did not match the filename");
            } else {
                musicAlbum.merge(temp);
            }
        }

        //
        // save the mp3s id3 tags in the file, and the db
        if (musicDb != null) {

            musicDb.beginSave();

            try {
                musicDb.saveAlbum(musicAlbum);
            } catch (MusicDbException ex) {
                caller.status(MsgType.ERROR, "database save failed");
            }

            musicDb.endSave();
        }

        ID3Version tagV = ID3Version.ALL;
        boolean cleanupTags = settings.getCleanupTags();
        boolean cleanupOnly = false;

        if (settings.getWriteId3v1() && !settings.getWriteId3v2()) {
            tagV = ID3Version.V1;
        }
        if (!settings.getWriteId3v1() && settings.getWriteId3v2()) {
            tagV = ID3Version.V2;
        }

        if (!settings.getWriteId3v1() && !settings.getWriteId3v2()) {
            cleanupOnly = cleanupTags;
        }

        for (MusicTrack mp3 : musicAlbum.getTracks()) {
            boolean needSaving = false;

            try {
                mp3p.init(mp3.getFile(), tagV);
            } catch (Id3ProxyException ex) {
                Logger.getLogger(TagBuilder.class.getName()).log(Level.SEVERE, null, ex);
                continue;
            }
            if (cleanupTags || cleanupOnly) {
                mp3p.cleanAllTags();
                needSaving = true;
            }

            if (!cleanupOnly && applyTagChanges(mp3p, mp3)) {
                needSaving = true;
            }

            if (needSaving) {
                try {
                    mp3p.save();
                } catch (Id3ProxyException ex) {
                    Logger.getLogger(TagBuilder.class.getName()).log(Level.SEVERE, null, ex);
                    continue;
                }
            }

            caller.status(MsgType.PROGRESS, StringHelper.substitute(getString("job-progress"), Thread.currentThread().getId() + "|" + musicAlbum.getName() + "|" + mp3.getFile().getName()));
            caller.updateProgress(1);

        }

        caller.updateProgress(1);

        caller.status(MsgType.JOB_COMPLETED, StringHelper.substitute(getString("job-completed"), Thread.currentThread().getId() + "|" + album.getName() + "|" + files.size() + "|" + Long.toString(System.currentTimeMillis() - startTime) + "|" + album.getId()));
    }

    private boolean applyTagChanges(Id3Proxy mp3p, MusicTrack mp3) {

        boolean changes = false;
        // for each tags, extract and same in the mp3

        if (mp3.getArtist() != null) {
            try {
                mp3p.setArtist(mp3.getArtist());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Artist", mp3.getFile().getPath(), mp3.getArtist());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Artist", mp3p.getLibName());
            }
        }

        if (mp3.getAlbum() != null) {
            try {
                mp3p.setAlbum(mp3.getAlbum());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Album", mp3.getFile().getPath(), mp3.getAlbum());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Album", mp3p.getLibName());
            }
        }

        if (mp3.getTitle() != null) {
            try {
                mp3p.setTitle(mp3.getTitle());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Title", mp3.getFile().getPath(), mp3.getTitle());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Title", mp3p.getLibName());
            }
        }

        if (mp3.getYear() != 0) {
            try {
                mp3p.setYear(String.valueOf(mp3.getYear()));
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Year", mp3.getFile().getPath(), String.valueOf(mp3.getYear()));
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Year", mp3p.getLibName());
            }
        }

        if (mp3.getComment() != null) {
            try {
                mp3p.setComment(mp3.getComment());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Comment", mp3.getFile().getPath(), mp3.getComment());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Comment", mp3p.getLibName());
            }
        }

        if (mp3.getCoverArt() != null && settings.getEmbedArt()) {
            try {
                mp3p.setCoverArt(mp3.getCoverArt());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "CoverArt", mp3.getFile().getPath(), mp3.getComment());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "CoverArt", mp3p.getLibName());
            }
        }

        if (mp3.getGenre() != null) {
            try {
                mp3p.setGenre(mp3.getGenre());
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Genre", mp3.getFile().getPath(), mp3.getGenre());
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Genre", mp3p.getLibName());
            }
        }

        if (mp3.getTrackNo() != 0) {
            String trackNo = String.valueOf(mp3.getTrackNo());

            if (settings.getNormTrackNo()) {
                if (mp3.getTrackNo() < 10) {
                    trackNo = "0" + trackNo;
                }
                if (mp3.getDiscCount() > 1) {
                    trackNo = mp3.getDiscNo() + trackNo;
                }
            }

            try {
                mp3p.setTrackNo(trackNo);
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Track", mp3.getFile().getPath(), String.valueOf(mp3.getTrackNo()));
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Track", mp3p.getLibName());
            }
        }
        if (mp3.getDiscNo() != 0) {
            try {
                mp3p.setDiscNo(String.valueOf(mp3.getDiscNo()));
                changes = true;
            } catch (Id3ProxyDataException ex) {
                handleException(ex, "Disc", mp3.getFile().getPath(), String.valueOf(mp3.getTrackNo()));
            } catch (Id3ProxyUnsupportedException ex) {
                handleException(ex, "Disc", mp3p.getLibName());
            }
        }
        /**
         * @todo: need to add proper support for v2 (performer, etc.) tags
         */
        return changes;
    }

    private Id3Proxy getTagProxy() {

        Id3Proxy mp3p;

        /*if (settings.getTagLib().equalsIgnoreCase("jid3-0.46")) {
        mp3p = new JId3Proxy();
        } else if (settings.getTagLib().equalsIgnoreCase("jid3lib-0.5.4")) {
        mp3p = new JId3libProxy();
        } else {*/
        mp3p = new JAudioTaggerProxy();
        //}



        return mp3p;


    }

    private MusicAlbum findAlbumArt() {

        MusicAlbum musicAlbum = new MusicAlbum();

        File albumArt = FileHelper.getFileByName(album.getUri(), settings.getArtFilenames());



        if (albumArt != null) {
            if (albumArt.length() > settings.getAlbumArtSize()) {
                String[] params = {album.getName(),
                    StringHelper.relPath(albumArt.getPath(), album.getName()),
                    Integer.toString(settings.getAlbumArtSize() / 1024)};
                caller.status(MsgType.WARNING, StringHelper.substitute(getString("warning-cover_size"), params));


            } else if (albumArt.length() > settings.getAlbumArtSize() * 2) {
                String[] params = {album.getName(),
                    StringHelper.relPath(albumArt.getPath(), album.getName()),
                    Integer.toString(settings.getAlbumArtSize() * 2 / 1024)};
                caller.status(MsgType.ERROR, StringHelper.substitute(getString("error-cover_size"), params));


            }

            musicAlbum.setCoverArt(albumArt.getPath());


        }

        return musicAlbum;


    }

    private MusicAlbum extract(File mp3, String[] supportedExt) {

        MusicAlbum musicAlbum = null;

        String mp3Uri = StringHelper.relPath(mp3.getPath(), album.getName());
        // ensure all extensions are treated as lowercase
        mp3Uri = mp3Uri.substring(0, mp3Uri.lastIndexOf(".")) + mp3Uri.substring(mp3Uri.lastIndexOf(".")).toLowerCase();
        // regexp remove all supported extensions - there should not be non-supported files at this point.
        mp3Uri = mp3Uri.replaceAll("\\.(" + StringHelper.join(supportedExt, "|") + ")$", "");
        Matcher m = format.getPattern().matcher(mp3Uri.replace("\\", "/"));



        if (m.matches()) {

            musicAlbum = new MusicAlbum();
            MusicTrack musicTrack = new MusicTrack();
            musicAlbum.addTrack(musicTrack);

            musicTrack.setFile(mp3);

            ArrayList<String> ph = format.getPlaceholders();

            //
            // then let's loop on all tags, and extract what we can


            for (int i = 1; i < ph.size(); i++) {

                String tag = ph.get(i);


                if (tag.equals("") || m.group(i) == null || m.group(i).equals("")) {
                    continue;


                } else if (tag.equalsIgnoreCase("title")) {
                    musicTrack.setTitle(m.group(i));


                } else if (tag.equalsIgnoreCase("artist")) {
                    musicAlbum.setArtist(m.group(i));


                } else if (tag.equalsIgnoreCase("trackartist") || tag.equalsIgnoreCase("discartist")) {
                    musicTrack.setArtist(m.group(i));


                } else if (tag.equalsIgnoreCase("album")) {
                    musicAlbum.setName(m.group(i));


                } else if (tag.equalsIgnoreCase("year")) {
                    musicAlbum.setYear(Integer.parseInt(m.group(i)));


                } else if (tag.equalsIgnoreCase("trackyear") || tag.equalsIgnoreCase("discyear")) {
                    musicTrack.setYear(Integer.parseInt(m.group(i)));


                } else if (tag.equalsIgnoreCase("comment")) {
                    musicTrack.setComment(m.group(i));


                } else if (tag.equalsIgnoreCase("genre")) {
                    musicTrack.setGenre(m.group(i));


                } else if (tag.equalsIgnoreCase("track")) {
                    musicTrack.setTrackNo(Integer.parseInt(m.group(i)));


                } else if (tag.equalsIgnoreCase("discno")) {
                    musicTrack.setDiscNo(Integer.parseInt(m.group(i)));


                } else if (tag.equalsIgnoreCase("disccount")) {
                    musicAlbum.setDiscCount(StringHelper.wordsToInt(m.group(i)));


                }

            }

            if (musicAlbum.getYear() < 0 && !musicAlbum.getName().equals("")) {

                try {

                    Pattern p = Pattern.compile(".*\\s((19|20)[\\d]{2,2}).*");
                    m = p.matcher(musicAlbum.getName());


                    if (m.matches()) {
                        musicAlbum.setYear(Integer.parseInt(m.group(1)));


                    }

                } catch (PatternSyntaxException pex) {
                    // because the pattern is hardcoded, there's no
                    // need to do anything here - this excpt will never
                    // be raised.
                }
            }
            musicTrack.setMusicAlbum(musicAlbum);


        }
        return musicAlbum;


    }

    /**
     *
     * @param key
     * @return
     */
    protected String getString(String key) {
        if (messages == null) {
            messages = ResourceBundle.getBundle("com.empireone.id3tagbuilder.Messages", settings.getLocale());


        }

        return messages.getString(key);


    }

    /**
     *
     * @param ex
     * @param tag
     * @param lib
     */
    protected void handleException(Id3ProxyUnsupportedException ex, String tag, String lib) {
        caller.status(MsgType.ERROR_UNSUPPORTED, StringHelper.substitute(getString("error-unsupported"), Thread.currentThread().getId() + "|" + tag + "|" + lib));


    }

    /**
     *
     * @param ex
     * @param tag
     * @param mp3
     * @param data
     */
    protected void handleException(Id3ProxyDataException ex, String tag, String mp3, String data) {
        caller.status(MsgType.ERROR_DATA, StringHelper.substitute(getString("error-data"), Thread.currentThread().getId() + "|" + tag + "|" + mp3 + "|" + data));

    }
}
